#include "musicplayer.h"


#include "volumebutton.h"

#include <QtWidgets>
#include <QtWinExtras>

//! mime是一种表示媒体类型的标识符，全称为Multipurpose Internet Mail Extensions。
//! 它通常用于标识互联网上的各种媒体数据的格式和类型，例如音频、视频、图像等。

MusicPlayer::MusicPlayer(QWidget *parent) : QWidget(parent)
{
    createWidgets();
    createShortcuts();
    createJumpList();
    createTaskbar();
    createThumbnailToolBar();

    connect(&mediaPlayer, &QMediaPlayer::positionChanged, this, &MusicPlayer::updatePosition);
    connect(&mediaPlayer, &QMediaPlayer::durationChanged, this, &MusicPlayer::updateDuration);
    connect(&mediaPlayer, &QMediaObject::metaDataAvailableChanged, this, &MusicPlayer::updateInfo);

    // 当mediaPlayer对象在播放音乐过程中发生错误时，会发出error信号。通过使用QOverload模板函数，可以将错误信号与handleError槽函数进行连接。
    connect(&mediaPlayer, QOverload<QMediaPlayer::Error>::of(&QMediaPlayer::error),
            this, &MusicPlayer::handleError);
    connect(&mediaPlayer, &QMediaPlayer::stateChanged,
            this, &MusicPlayer::updateState);

    stylize();
    setAcceptDrops(true);   // 支持拖动
}

// 如果返回的列表为空，说明系统没有默认的媒体类型支持，那么会手动添加一个默认的媒体类型"audio/mpeg"到结果列表中。
QStringList MusicPlayer::supportedMimeTypes()
{
    QStringList result = QMediaPlayer::supportedMimeTypes();
    if (result.isEmpty())
        result.append(QStringLiteral("audio/mpeg"));
    return result;
}

// 打开MP3文件
void MusicPlayer::openFile()
{
    QFileDialog fileDialog(this);
    fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
    fileDialog.setWindowTitle(tr("打开MP3文件"));
    fileDialog.setMimeTypeFilters(MusicPlayer::supportedMimeTypes());
    fileDialog.setDirectory(QStandardPaths::standardLocations(QStandardPaths::MusicLocation).value(0, QDir::homePath()));
    if (fileDialog.exec() == QDialog::Accepted)
        playUrl(fileDialog.selectedUrls().constFirst());
}

// 这段代码的作用是根据指定的URL资源进行音乐播放，并更新相关的UI显示。
void MusicPlayer::playUrl(const QUrl &url)
{
    qDebug()<<" url = "<<url.path()<<" url.toLocalFile() = "<<url.toLocalFile();

    playButton->setEnabled(true);
    if (url.isLocalFile()) {
        if (url.toString().contains(":/")) { // 资源文件夹MP3c 好像播放不了
            const QString filePath = url.toLocalFile();
            setWindowFilePath(filePath);
            infoLabel->setText(filePath);
            fileName = QFileInfo(filePath).fileName();
        }
        else { // 外部文件
            const QString filePath = url.toLocalFile();
            setWindowFilePath(filePath);
            infoLabel->setText(QDir::toNativeSeparators(filePath));
            fileName = QFileInfo(filePath).fileName();
        }
    } else {
        setWindowFilePath(QString());
        infoLabel->setText(url.toString());
        fileName.clear();
    }
    mediaPlayer.setMedia(url);
    mediaPlayer.play();
}

// togglePlayback函数用于切换播放状态，根据当前的媒体状态和播放器状态来决定下一步的操作。
// 如果没有媒体文件，则会调用openFile函数来打开文件。如果当前处于播放状态，则会暂停播放，否则会开始播放。
void MusicPlayer::togglePlayback()
{
    if (mediaPlayer.mediaStatus() == QMediaPlayer::NoMedia)
        openFile();
    else if (mediaPlayer.state() == QMediaPlayer::PlayingState)
        mediaPlayer.pause();
    else
        mediaPlayer.play();
}

// seekForward函数用于实现快进功能，它会触发positionSlider的SliderPageStepAdd操作，使得滑块向前移动一定的步长。
void MusicPlayer::seekForward()
{
    positionSlider->triggerAction(QSlider::SliderPageStepAdd);
}

// seekBackward函数用于实现快退功能，它会触发positionSlider的SliderPageStepSub操作，使得滑块向后移动一定的步长。
void MusicPlayer::seekBackward()
{
    positionSlider->triggerAction(QSlider::SliderPageStepSub);
}

//! [这段代码是音乐播放器的事件处理函数。它通过判断事件的类型来决定是否需要执行stylize函数。在这个例子中，
//! 如果事件是CompositionChange或ColorizationChange类型的事件，就会调用stylize函数来改变音乐播放器的样式。
//! 最后，它还会调用父类的event函数来处理其他类型的事件。这段代码的作用是根据特定的事件类型来对音乐播放器进行样式的调整。]
bool MusicPlayer::event(QEvent *event)
{
    if (event->type() == QWinEvent::CompositionChange || event->type() == QWinEvent::ColorizationChange)
        stylize();
    return QWidget::event(event);
}
//! [end]

//! [这段代码是音乐播放器的showEvent函数。在窗口显示时，首先调用了QWidget的showEvent函数。然后，通过判断taskbarButton的window是否为空，
//! 来确定是否需要对taskbarButton和thumbnailToolBar进行初始化。如果taskbarButton的window为空，
//! 就会将当前窗口的windowHandle设置给taskbarButton和thumbnailToolBar。
//! 这段代码的作用是在音乐播放器窗口显示时，进行任务栏按钮和缩略图工具栏的初始化。]
void MusicPlayer::showEvent(QShowEvent *event)
{
    QWidget::showEvent(event);
    if (!taskbarButton->window()) {
        auto window = windowHandle();
        taskbarButton->setWindow(window);
        thumbnailToolBar->setWindow(window);
    }
}
//! [end]

// 使用QMimeDatabase来获取URL的MIME类型，并与MusicPlayer类支持的MIME类型进行比较。如果支持该MIME类型，则返回true，表示可以处理该拖放事件。
// 这段代码的作用是确保只有支持的文件类型才能被拖放到音乐播放器中。
static bool canHandleDrop(const QDropEvent *event)
{
    const QList<QUrl> urls = event->mimeData()->urls();
    if (urls.size() != 1)
        return false;
    QMimeDatabase mimeDatabase;
    return MusicPlayer::supportedMimeTypes().contains(mimeDatabase.mimeTypeForUrl(urls.constFirst()).name());
}

// 这段代码是音乐播放器的拖放进入事件处理函数。当用户将文件拖入音乐播放器时，通过调用canHandleDrop函数来判断是否可以处理该拖放事件。
// 如果可以处理，则调用"event->setAccepted(true);"来接受该事件，表示该事件已经被处理。
void MusicPlayer::dragEnterEvent(QDragEnterEvent *event)
{
    event->setAccepted(canHandleDrop(event));
}

// 这两段代码是音乐播放器的鼠标事件处理函数。第一段代码是拖放事件处理函数，当用户将文件拖放到音乐播放器上时，调用playUrl函数来播放第一个文件的URL。
// 通过"event->accept();"表示该事件已经被处理。

// 第二段代码是鼠标点击事件处理函数，当鼠标点击音乐播放器时，记录鼠标点击位置与音乐播放器位置的偏移量，用于后续的移动操作。
// 同样，通过"event->accept();"表示该事件已经被处理。
void MusicPlayer::dropEvent(QDropEvent *event)
{
    event->accept();
    playUrl(event->mimeData()->urls().constFirst());
}

void MusicPlayer::mousePressEvent(QMouseEvent *event)
{
    offset = event->globalPos() - pos();
    event->accept();
}

// accept()当事件被接受时，表示该事件已经被处理，不会再被其他对象继续处理。这样可以防止事件的冒泡或传递给其他对象。
void MusicPlayer::mouseMoveEvent(QMouseEvent *event)
{
    move(event->globalPos() - offset);
    event->accept();
}

void MusicPlayer::mouseReleaseEvent(QMouseEvent *event)
{
    offset = QPoint();
    event->accept();
}

//! [音乐样式化函数]
void MusicPlayer::stylize()
{
    /*
        如果当前操作系统版本低于Windows 8，并且启用了桌面组合特性，那么设置音乐播放器的窗口边框扩展到客户区域，并将窗口的背景设置为透明。
        同时，设置音乐播放器的样式表为"MusicPlayer { background: transparent; }"，即透明背景。
        如果当前操作系统版本低于Windows 8，并且未启用桌面组合特性，那么重置音乐播放器的窗口边框扩展，并将窗口的背景设置为系统的颜色化颜色。
        同时，设置音乐播放器的样式表为"MusicPlayer { background: %1; }"，其中%1是实际的颜色化颜色。
        最后，调用volumeButton的stylize函数，对音量按钮进行样式化。
    */
    // 当前系统版本 小于 Windows8
    if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8) {
        // 设置仅与windows 7相关的样式选项。
        if (QtWin::isCompositionEnabled()) {    // 根据当前的操作系统版本和是否启用了桌面组合特性，设置不同的样式选项。
            QtWin::extendFrameIntoClientArea(this, -1, -1, -1, -1);
            setAttribute(Qt::WA_TranslucentBackground, true);
            setAttribute(Qt::WA_NoSystemBackground, false);
            setStyleSheet(QStringLiteral("MusicPlayer { background: transparent; }"));
        } else {
            QtWin::resetExtendedFrame(this);
            setAttribute(Qt::WA_TranslucentBackground, false);
            setStyleSheet(QStringLiteral("MusicPlayer { background: %1; }").arg(QtWin::realColorizationColor().name()));
        }

        // 对音量按钮进行样式化
        volumeButton->stylize();
    }
}
//! [end]

// 更新音乐播放状态
void MusicPlayer::updateState(QMediaPlayer::State state)
{
    if (state == QMediaPlayer::PlayingState) {
        playButton->setToolTip(tr("暂停"));
        playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPause));
    } else {
        playButton->setToolTip(tr("播放"));
        playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
    }
}

// 用于将毫秒表示的时间格式化为分:秒的形式
static QString formatTime(qint64 timeMilliSeconds)
{
    qint64 seconds = timeMilliSeconds / 1000;
    const qint64 minutes = seconds / 60;
    seconds -= minutes * 60;    // 总秒数减去-分钟里面占用的秒数 = 剩余的秒数
    return QStringLiteral("%1:%2")
        .arg(minutes, 2, 10, QLatin1Char('0'))
        .arg(seconds, 2, 10, QLatin1Char('0'));
}

// 在音乐播放器界面上更新当前播放位置，并将当前位置的时间显示出来。这样用户就可以清楚地知道当前音乐的播放进度。
void MusicPlayer::updatePosition(qint64 position)
{
    positionSlider->setValue(position);
    positionLabel->setText(formatTime(position));
}

// 更新音乐时间
void MusicPlayer::updateDuration(qint64 duration)
{
    // 这段代码是一个槽函数，用于更新音乐播放器的总时长。首先，将音乐播放器的总时长设置为滑动条的范围。如果总时长大于0，则启用滑动条，并设置滑动条的页面步长为总时长的1/10。
    // 然后，调用updateInfo函数来更新音乐文件的相关信息。
    positionSlider->setRange(0, duration);
    positionSlider->setEnabled(duration > 0);
    positionSlider->setPageStep(duration / 10);
    this->updateInfo();
}

void MusicPlayer::setPosition(int position)
{
    // 这段代码的目的是确保音乐播放器在跳转到指定位置时的精确性。如果当前位置与所需位置相差较大，可能是由于音乐播放器在跳转时存在一些误差。
    // 因此，通过比较当前位置和所需位置的差值，如果差值大于99毫秒，则将音乐播放器的位置设置为所需位置，以确保跳转到准确的位置。
    if (qAbs(mediaPlayer.position() - position) > 99)
        mediaPlayer.setPosition(position);
}

// 更新信息 这段代码的作用是在标签上显示音乐文件的相关信息，包括文件名、作者、标题和时长。
void MusicPlayer::updateInfo()
{
    QStringList info;
    if (!fileName.isEmpty())
        info.append(fileName);
    if (mediaPlayer.isMetaDataAvailable()) { // 音乐文件的元数据
        QString author = mediaPlayer.metaData(QStringLiteral("Author")).toString();
        if (!author.isEmpty())
            info.append(author);
        QString title = mediaPlayer.metaData(QStringLiteral("Title")).toString();
        if (!title.isEmpty())
            info.append(title);
    }
    info.append(formatTime(mediaPlayer.duration()));
    infoLabel->setText(info.join(tr(" - "))); // 使用" - "作为分隔符
}

// 操作错误
void MusicPlayer::handleError()
{
    playButton->setEnabled(false);
    const QString errorString = mediaPlayer.errorString();
    infoLabel->setText(errorString.isEmpty()
                       ? tr("未知错误 #%1").arg(int(mediaPlayer.error()))
                       : tr("错误: %1").arg(errorString));
}

//! [更新任务栏按钮和进度条的显示]
void MusicPlayer::updateTaskbar()
{
    switch (mediaPlayer.state()) {
    case QMediaPlayer::PlayingState: // 表示正在播放，任务栏按钮会显示播放图标，进度条会显示并继续播放。
        taskbarButton->setOverlayIcon(style()->standardIcon(QStyle::SP_MediaPlay));
        taskbarProgress->show();
        taskbarProgress->resume();   // resume继续
        break;
    case QMediaPlayer::PausedState: // 表示暂停播放，任务栏按钮会显示暂停图标，进度条会显示并暂停。
        taskbarButton->setOverlayIcon(style()->standardIcon(QStyle::SP_MediaPause));
        taskbarProgress->show();
        taskbarProgress->pause();
        break;
    case QMediaPlayer::StoppedState: // 表示停止播放，任务栏按钮会显示停止图标，进度条会隐藏。
        taskbarButton->setOverlayIcon(style()->standardIcon(QStyle::SP_MediaStop));
        taskbarProgress->hide();
        break;
    }
}
//! [end]

//! [更新缩略图工具栏]
void MusicPlayer::updateThumbnailToolBar()
{
    playToolButton->setEnabled(mediaPlayer.duration() > 0);         // 如果音乐播放器的总时长大于0，则启用播放按钮。
    backwardToolButton->setEnabled(mediaPlayer.position() > 0);     // 如果音乐播放器的当前播放位置大于0，则启用后退按钮。
    forwardToolButton->setEnabled(mediaPlayer.position() < mediaPlayer.duration()); // 如果音乐播放器的当前播放位置小于总时长，则启用前进按钮。

    if (mediaPlayer.state() == QMediaPlayer::PlayingState) {
        playToolButton->setToolTip(tr("暂停"));
        playToolButton->setIcon(style()->standardIcon(QStyle::SP_MediaPause));
    } else {
        playToolButton->setToolTip(tr("播放"));
        playToolButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
    }
}
//! [end]

// 创建界面小部件
void MusicPlayer::createWidgets()
{
    // 工具播放按钮图标
    playButton = new QToolButton(this);
    playButton->setEnabled(false);
    playButton->setToolTip(tr("播放"));   // 鼠标定位在图标上面的提示信息
    playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); // 获取Qt自带图标
    connect(playButton, &QAbstractButton::clicked, this, &MusicPlayer::togglePlayback); // 关联槽函数 切換重播

    // 打开文件工具按钮
    QAbstractButton *openButton = new QToolButton(this);
    openButton->setText(tr("..."));
    openButton->setToolTip(tr("打开一个文件..."));
    openButton->setFixedSize(playButton->sizeHint());   // sizeHint()是一个函数，用于获取控件的推荐大小。
    connect(openButton, &QAbstractButton::clicked, this, &MusicPlayer::openFile);

    // 音量工具按钮
    volumeButton = new VolumeButton(this);
    volumeButton->setToolTip(tr("调整音量"));
    volumeButton->setVolume(mediaPlayer.volume());
    connect(volumeButton, &VolumeButton::volumeChanged, &mediaPlayer, &QMediaPlayer::setVolume);

    positionSlider = new QSlider(Qt::Horizontal, this);
    positionSlider->setEnabled(false);
    positionSlider->setToolTip(tr("滑动条"));
    connect(positionSlider, &QAbstractSlider::valueChanged, this, &MusicPlayer::setPosition);

    // 标签信息
    infoLabel = new QLabel(this);
    // 播放时间标签
    positionLabel = new QLabel(tr("00:00"), this);
    positionLabel->setMinimumWidth(positionLabel->sizeHint().width());

    // 水平布局 从左到右布局
    QBoxLayout *controlLayout = new QHBoxLayout;
    controlLayout->setMargin(0);    // 外边距0px
    controlLayout->addWidget(openButton);
    controlLayout->addWidget(playButton);
    controlLayout->addWidget(positionSlider);
    controlLayout->addWidget(positionLabel);
    controlLayout->addWidget(volumeButton);

    // 垂直布局 从上到下布局
    QBoxLayout *mainLayout = new QVBoxLayout(this); // 【1】指定父对象 父对象释放资源时这个类也自动释放了
    mainLayout->addWidget(infoLabel);
    mainLayout->addLayout(controlLayout);           // 将一个对象controlLayout放进另一个mainLayout,那么创建controlLayout时可以不指定父对象，跟【1】一个道理
}

// 创建快捷键
void MusicPlayer::createShortcuts()
{
    QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
    connect(quitShortcut, &QShortcut::activated, QCoreApplication::quit); // 按q退出应用程序 如果没退出说明这个快捷键可能被其他程序占用了

    QShortcut *openShortcut = new QShortcut(QKeySequence::Open, this); // ctrl+o
    connect(openShortcut, &QShortcut::activated, this, &MusicPlayer::openFile);

    QShortcut *toggleShortcut = new QShortcut(Qt::Key_Space, this);     // 空格键
    connect(toggleShortcut, &QShortcut::activated, this, &MusicPlayer::togglePlayback);

    // 上↑下↓ 左←右→ 箭头
    QShortcut *forwardShortcut = new QShortcut(Qt::Key_Right, this);
    connect(forwardShortcut, &QShortcut::activated, this, &MusicPlayer::seekForward);   // 快进

    QShortcut *backwardShortcut = new QShortcut(Qt::Key_Left, this);
    connect(backwardShortcut, &QShortcut::activated, this, &MusicPlayer::seekBackward); // 后退

    QShortcut *increaseShortcut = new QShortcut(Qt::Key_Up, this);
    connect(increaseShortcut, &QShortcut::activated, volumeButton, &VolumeButton::increaseVolume); // 增加音量 如果没说明这个快捷键可能被其他程序占用了


    QShortcut *decreaseShortcut = new QShortcut(Qt::Key_Down, this);
    connect(decreaseShortcut, &QShortcut::activated, volumeButton, &VolumeButton::descreaseVolume); // 音量下降 如果没说明这个快捷键可能被其他程序占用了
}

//! [创建跳转列表]
void MusicPlayer::createJumpList()
{
    /* `QWinJumpList`是Qt框架中的一个类，用于管理Windows任务栏中的Jump List。在上述代码中，`QWinJumpList jumplist;`创建了一个`QWinJumpList`对象，
     * 然后通过`jumplist.recent()->setVisible(true);`将最近使用的项目显示在Jump List中。
     * 这样可以为用户提供方便的访问最近使用的项目的功能。*/
    QWinJumpList jumplist;
    jumplist.recent()->setVisible(true);
}
//! [end]

//! [创建任务栏]
void MusicPlayer::createTaskbar()
{
    taskbarButton = new QWinTaskbarButton(this);

    taskbarProgress = taskbarButton->progress(); // 用于在任务栏按钮上显示进度条
    connect(positionSlider, &QAbstractSlider::valueChanged, taskbarProgress, &QWinTaskbarProgress::setValue);
    connect(positionSlider, &QAbstractSlider::rangeChanged, taskbarProgress, &QWinTaskbarProgress::setRange);

    connect(&mediaPlayer, &QMediaPlayer::stateChanged, this, &MusicPlayer::updateTaskbar);
}
//! [end]

//! [创建缩略图工具栏]
void MusicPlayer::createThumbnailToolBar()
{
    thumbnailToolBar = new QWinThumbnailToolBar(this);

    // “播放工具”按钮
    playToolButton = new QWinThumbnailToolButton(thumbnailToolBar);
    playToolButton->setEnabled(false);
    playToolButton->setToolTip(tr("播放"));
    playToolButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
    connect(playToolButton, &QWinThumbnailToolButton::clicked, this, &MusicPlayer::togglePlayback);

    // 快进工具按钮图标
    forwardToolButton = new QWinThumbnailToolButton(thumbnailToolBar);
    forwardToolButton->setEnabled(false);
    forwardToolButton->setToolTip(tr("快进键"));
    forwardToolButton->setIcon(style()->standardIcon(QStyle::SP_MediaSeekForward));
    connect(forwardToolButton, &QWinThumbnailToolButton::clicked, this, &MusicPlayer::seekForward);

    // 倒退工具按钮图标
    backwardToolButton = new QWinThumbnailToolButton(thumbnailToolBar);
    backwardToolButton->setEnabled(false);
    backwardToolButton->setToolTip(tr("倒回"));
    backwardToolButton->setIcon(style()->standardIcon(QStyle::SP_MediaSeekBackward));
    connect(backwardToolButton, &QWinThumbnailToolButton::clicked, this, &MusicPlayer::seekBackward);

    thumbnailToolBar->addButton(backwardToolButton);
    thumbnailToolBar->addButton(playToolButton);
    thumbnailToolBar->addButton(forwardToolButton);

    connect(&mediaPlayer, &QMediaPlayer::positionChanged, this, &MusicPlayer::updateThumbnailToolBar);
    connect(&mediaPlayer, &QMediaPlayer::durationChanged, this, &MusicPlayer::updateThumbnailToolBar);
    connect(&mediaPlayer, &QMediaPlayer::stateChanged, this, &MusicPlayer::updateThumbnailToolBar);
}
//! [end]
